#pragma once

#include <engine.core/math.hpp>

#include "D3D11Declarations.hpp"




/**
 * All this function does is caching the values given to various functions (like IAVSSetShader)
 * and thus avoiding overhead by too many API calls.
 */
class D3D11DeviceContext
{
private:

	/// A handle to the actual context
	ID3D11DeviceContextPtr    m_context;

	ID3D11VertexShader*       m_vertexShader;
	ID3D11PixelShader*        m_pixelShader;
	ID3D11GeometryShader*     m_geometryShader;
	ID3D11DomainShader*       m_domainShader;
	ID3D11HullShader*         m_hullShader;
	ID3D11ComputeShader*      m_computeShader;

public:

	D3D11DeviceContext()
	{}

	D3D11DeviceContext(const ID3D11DeviceContextPtr& context)
		: m_context(context)
	{}



	/**
	 * Clears the temporary cache of this context.
	 */
	void Clear()
	{
		//m_context->ClearState();

		m_vertexShader = NULL;
		m_pixelShader = NULL;
		m_geometryShader = NULL;
		m_domainShader = NULL;
		m_hullShader = NULL;
		m_computeShader = NULL;
	}

	void ClearRenderTargetView(const ID3D11RenderTargetViewPtr& renderTargetView, const float4& color)
	{
		m_context->ClearRenderTargetView(renderTargetView.get(), &color.x);
	}

	void IASetInputLayout(const ID3D11InputLayoutPtr& inputLayout)
	{
		m_context->IASetInputLayout(inputLayout.get());
	}

	operator const ID3D11DeviceContextPtr&() const
	{
		return m_context;
	}

	void RSSetViewports(uint32 NumViewports, const D3D11_VIEWPORT *pViewport)
	{
		m_context->RSSetViewports(NumViewports, pViewport);
	}

	void RSSetState(ID3D11RasterizerState *pRasterizerState)
	{
		m_context->RSSetState(pRasterizerState);
	}

	void OMSetRenderTargets(uint32 NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView)
	{
		m_context->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView);
	}

	void OMSetBlendState(ID3D11BlendState *pBlendState, const float BlendFactor[4] = NULL, uint32 SampleMask = std::numeric_limits<uint32>::max())
	{
		m_context->OMSetBlendState(pBlendState, BlendFactor, SampleMask);
	}

	void VSSetShader(const ID3D11VertexShaderPtr& vertexShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		/*if(vertexShader == m_vertexShader)
			return;*/

		m_context->VSSetShader(vertexShader.get(), ppClassInstances, NumClassInstances);
		//m_vertexShader = vertexShader.get();
	}

	void PSSetShader(const ID3D11PixelShaderPtr& pixelShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		/*if(m_pixelShader == pixelShader)
			return;*/

		m_context->PSSetShader(pixelShader.get(), ppClassInstances, NumClassInstances);
		//m_pixelShader = pixelShader.get();
	}

	void DSSetShader(const ID3D11DomainShaderPtr& domainShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		if(m_domainShader == domainShader)
			return;

		m_context->DSSetShader(domainShader.get(), ppClassInstances, NumClassInstances);
		m_domainShader = domainShader.get();
	}

	void CSSetShader(const ID3D11ComputeShaderPtr& computeShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		if(m_computeShader == computeShader)
			return;

		m_context->CSSetShader(computeShader.get(), ppClassInstances, NumClassInstances);
		m_computeShader = computeShader.get();
	}

	void GSSetShader(const ID3D11GeometryShaderPtr& geometryShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		if(m_geometryShader == geometryShader)
			return;

		m_context->GSSetShader(geometryShader.get(), ppClassInstances, NumClassInstances);
		m_geometryShader = geometryShader.get();
	}

	void HSSetShader(const ID3D11HullShaderPtr& hullShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{
		if(m_hullShader == hullShader)
			return;

		m_context->HSSetShader(hullShader.get(), ppClassInstances, NumClassInstances);
		m_hullShader = hullShader.get();
	}



	/**
	 * Set a shader.
	 */
	template <typename Type>
	void setShader(const COM_PTR(Type)& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances);

	template <>
	void setShader<ID3D11VertexShader>(const ID3D11VertexShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ VSSetShader(shader, ppClassInstances, NumClassInstances); }

	template <>
	void setShader<ID3D11PixelShader>(const ID3D11PixelShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ PSSetShader(shader, ppClassInstances, NumClassInstances); }

	template <>
	void setShader<ID3D11GeometryShader>(const ID3D11GeometryShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ GSSetShader(shader, ppClassInstances, NumClassInstances); }

	template <>
	void setShader<ID3D11HullShader>(const ID3D11HullShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ HSSetShader(shader, ppClassInstances, NumClassInstances); }

	template <>
	void setShader<ID3D11DomainShader>(const ID3D11DomainShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ DSSetShader(shader, ppClassInstances, NumClassInstances); }

	template <>
	void setShader<ID3D11ComputeShader>(const ID3D11ComputeShaderPtr& shader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances)
	{ CSSetShader(shader, ppClassInstances, NumClassInstances); }



	/**
	 * Set samplers.
	 */
	template <typename Shader> void setSamplers(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers);
	template <> void setSamplers<ID3D11VertexShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); }
	template <> void setSamplers<ID3D11PixelShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); }
	template <> void setSamplers<ID3D11GeometryShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->GSSetSamplers(StartSlot, NumSamplers, ppSamplers); }
	template <> void setSamplers<ID3D11DomainShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->DSSetSamplers(StartSlot, NumSamplers, ppSamplers); }
	template <> void setSamplers<ID3D11HullShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->HSSetSamplers(StartSlot, NumSamplers, ppSamplers); }
	template <> void setSamplers<ID3D11ComputeShader>(uint32 StartSlot, uint32 NumSamplers, ID3D11SamplerState *const *ppSamplers) { m_context->CSSetSamplers(StartSlot, NumSamplers, ppSamplers); }



	/**
	 * Set constant buffers.
	 */
	template <typename Shader> void setConstantBuffers(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers);
	template <> void setConstantBuffers<ID3D11VertexShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }
	template <> void setConstantBuffers<ID3D11PixelShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }
	template <> void setConstantBuffers<ID3D11GeometryShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->GSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }
	template <> void setConstantBuffers<ID3D11DomainShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->DSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }
	template <> void setConstantBuffers<ID3D11HullShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->HSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }
	template <> void setConstantBuffers<ID3D11ComputeShader>(uint32 StartSlot, uint32 NumBuffers, ID3D11Buffer **ppConstantBuffers) { m_context->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); }



	/**
	 * Set shader resources
	 */
	template <typename Shader> void setShaderResources(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews);
	template <> void setShaderResources<ID3D11VertexShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
	template <> void setShaderResources<ID3D11PixelShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
	template <> void setShaderResources<ID3D11GeometryShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->GSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
	template <> void setShaderResources<ID3D11DomainShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->DSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
	template <> void setShaderResources<ID3D11HullShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->HSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
	template <> void setShaderResources<ID3D11ComputeShader>(uint32 StartSlot, uint32 NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) { m_context->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); }
};
///////////////////////////////////////////////////////////////////////////////////////////////////
